package com.smile.frame.workflow.flowable.demo.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smile.frame.workflow.flowable.demo.constants.BpmCommentTypeEnum;
import com.smile.frame.workflow.flowable.demo.constants.BpmConstants;
import com.smile.frame.workflow.flowable.demo.constants.BpmTaskStatusEnum;
import com.smile.frame.workflow.flowable.demo.service.BpmProcessInstanceService;
import com.smile.frame.workflow.flowable.demo.service.BpmTaskService;
import com.smile.frame.workflow.flowable.demo.service.BpmnModelService;
import com.smile.frame.workflow.flowable.demo.util.BpmnModelUtils;
import com.smile.frame.workflow.flowable.demo.util.FlowableUtils;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author LGC
 */
@Slf4j
@Service
public class BpmTaskServiceImpl implements BpmTaskService {
    @Resource
    private BpmnModelService bpmnModelService;
    @Resource
    private BpmProcessInstanceService bpmProcessInstanceService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;
    @Resource
    private RuntimeService runtimeService;

    @Override
    public Task getTask(String taskId) {
        return taskService.createTaskQuery().taskId(taskId).includeTaskLocalVariables().singleResult();
    }

    @Override
    public List<Task> queryMyTodoTask(String userId, List<String> candidateGroups, String businessKey) {
        if (StrUtil.isBlank(userId) && CollUtil.isEmpty(candidateGroups)) {
            return null;
        }
        // 查询待办事项列表
        TaskQuery taskQuery = taskService.createTaskQuery()
                .active() // 激活状态
                .includeProcessVariables()
                .orderByTaskCreateTime().desc();

        if (StrUtil.isNotBlank(userId)) {

            // flowable:assignee="1";
            // flowable:candidateUsers="1,2";

            taskQuery.taskCandidateOrAssigned(userId);
        }
        if (CollUtil.isNotEmpty(candidateGroups)) {
            // flowable:candidateGroups = "role:1"
            taskQuery.taskCandidateGroupIn(candidateGroups);
        }
        // 业务key
        if (StrUtil.isNotBlank(businessKey)) {
            taskQuery.processInstanceBusinessKey(businessKey);
        }

        List<Task> list = taskQuery.list();
        log.info("我的待办任务列表:{}", JSONUtil.toJsonStr(list));
        return list;
    }

    @Override
    public List<HistoricTaskInstance> queryMyFinishTask(String userId, String businessKey) {
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
                .finished() // 已完成
                .taskAssignee(userId) // 分配给自己
                .includeTaskLocalVariables()
                .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序

        // 业务key
        if (StrUtil.isNotBlank(businessKey)) {
            historicTaskInstanceQuery.processInstanceBusinessKey(businessKey);
        }
        List<HistoricTaskInstance> list = historicTaskInstanceQuery.list();
        log.info("我的已办任务列表:{}", JSONUtil.toJsonStr(list));
        return list;
    }


    private Task validateTask(String userId, String taskId) {
        Task task = getTask(taskId);
        if (task == null) {
            throw new RuntimeException("任务不存在");
        }
        if (task.getAssignee() != null && !Objects.equals(task.getAssignee(), userId)) {
            throw new RuntimeException("该任务的审批人不是你");
        }
        return task;
    }


    /**
     * 更新流程任务的 status 状态、reason 理由
     *
     * @param id     任务编号
     * @param status 状态
     * @param reason 理由（审批通过、审批不通过的理由）
     */
    private void updateTaskStatusAndReason(String id, Integer status, String reason) {
        updateTaskStatus(id, status);
        taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_REASON, reason);
    }

    /**
     * 更新流程任务的 status 状态
     *
     * @param id     任务编号
     * @param status 状态
     */
    private void updateTaskStatus(String id, Integer status) {
        taskService.setVariableLocal(id, BpmConstants.TASK_VARIABLE_STATUS, status);
    }


    @Override
    public void approvalTask(String userId, String taskId, Map<String, Object> variables, String reason) {
        log.info("任务审核通过start");
        Integer status = BpmTaskStatusEnum.APPROVE.getStatus(); // 审核通过
        // 当前用户上下文
        Authentication.setAuthenticatedUserId(userId);
        // 1.1根据任务ID查询出任务，并判断是否属于我的任务
        Task task = validateTask(userId, taskId);
        // 1.2 校验任务实例
        ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
        if (processInstance == null) {
            throw new RuntimeException("该任务的流程实例不存在");
        }
        String processInstanceId = processInstance.getId();
        // 情况一：审批被委派的任务，被委派的任务，不调用 complete 去完成任务
        Integer taskStatus = (Integer) task.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
        if (DelegationState.PENDING.equals(task.getDelegationState()) &&
                taskStatus.equals(BpmTaskStatusEnum.DELEGATE.getStatus())) {
            // 1. 添加审批意见
            String owner = task.getOwner();// 任务所有者，发起委托的人
            Assert.notNull(owner, "委派任务找不到原审批人，需要检查数据");
            taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_END.getType(),
                    BpmCommentTypeEnum.DELEGATE_END.formatComment(userId, task.getOwner(), reason));
            // 2.1 调用 resolveTask 完成任务。任务重新回到发起委托人手中（任务所有者）
            /**
             * 底层调用
             * task.setDelegationState(DelegationState.RESOLVED);
             * TaskHelper.changeTaskAssignee(task, task.getOwner())
             *
             * ACT_RU_TASK 表中  assignee = owner ,DELEGATION_ = RESOLVED
             */
            taskService.resolveTask(taskId);
            // 2.2 更新 task 状态 + 原因
            updateTaskStatusAndReason(taskId, status, reason);
            return;
        }

        // 1.3 设置任务本地变量 审核状态 和 原因
        updateTaskStatusAndReason(taskId, status, reason);
        // 1.4 添加评论
        taskService.addComment(taskId, processInstanceId,
                BpmCommentTypeEnum.APPROVE.getType(), BpmCommentTypeEnum.APPROVE.formatComment(reason));
        // 1.5 任务声明 进行任务代理人指定（效果类似于setAssignee方法）认领任务
        taskService.claim(taskId, userId);
        // 1.6 去完成任务
        if (CollUtil.isNotEmpty(variables)) {
            variables = FlowableUtils.filterTaskFormVariable(variables);
            taskService.complete(taskId, variables, true);
        } else {
            taskService.complete(taskId);
        }
        // 1.7 可发送任务审核通过事件，处理自身业务...
        log.info("任务审核通过end");
    }

    @Override
    public void rejectTask(String userId, String taskId, Map<String, Object> variables, String reason) {
        log.info("任务审核不通过start");
        Integer status = BpmTaskStatusEnum.REJECT.getStatus(); // 审核不通过
        // 当前用户上下文
        Authentication.setAuthenticatedUserId(userId);
        // 1.1根据任务ID查询出任务，并判断是否属于我的任务
        Task task = validateTask(userId, taskId);
        // 1.2 校验任务实例
        ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(task.getProcessInstanceId());
        if (processInstance == null) {
            throw new RuntimeException("该任务的流程实例不存在");
        }

        String processInstanceId = processInstance.getId();
        // 1.3 设置任务本地变量 审核状态 和 原因
        updateTaskStatusAndReason(taskId, status, reason);
        // 1.4 添加评论
        taskService.addComment(taskId, processInstanceId,
                BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reason));
        // 1.5 任务声明 进行任务代理人指定（效果类似于setAssignee方法）
        taskService.claim(taskId, userId);
        // 1.6 去完成任务
        if (CollUtil.isNotEmpty(variables)) {
            variables = FlowableUtils.filterTaskFormVariable(variables);
            taskService.complete(taskId, variables, true);
        } else {
            taskService.complete(taskId);
        }
        bpmProcessInstanceService.updateProcessInstanceReject(processInstanceId, reason);
        log.info("任务审核不通过end");
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delegateTask(String userId, String taskId, String delegateUserId, String reason) {
        // 当前用户上下文
        Authentication.setAuthenticatedUserId(userId);
        // 1.1根据任务ID查询出任务，并判断是否属于我的任务
        Task task = validateTask(userId, taskId);
        // todo 1.2 校验目标用户存在
        // 2. 添加委托意见
        taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.DELEGATE_START.getType(),
                BpmCommentTypeEnum.DELEGATE_START.formatComment(userId, delegateUserId, reason));
        // 领取任务（效果类似于setAssignee方法）
        taskService.claim(taskId, userId);
        // 3.1 设置任务所有者 (owner) 为原任务的处理人 (assignee)即任务发起人任务领取人
//        taskService.setOwner(taskId, task.getAssignee());
        taskService.setOwner(taskId, userId);
        // 3.2 执行委派，将任务委派给 delegateUser
        /**
         * 底层调用
         * task.setDelegationState(DelegationState.PENDING);
         * TaskHelper.changeTaskAssignee(task, userId);
         * ACT_RU_TASK 表中  assignee = owner ,DELEGATION_ = PENDING
         */
        taskService.delegateTask(taskId, delegateUserId);
        // 3.3 更新 task 状态。
        // 为什么不更新原因？因为原因目前主要给审批通过、不通过时使用
        updateTaskStatus(taskId, BpmTaskStatusEnum.DELEGATE.getStatus());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transferTask(String userId, String taskId, String assigneeUserId, String reason) {
        // 当前用户上下文
        Authentication.setAuthenticatedUserId(userId);
        // 1.1根据任务ID查询出任务，并判断是否属于我的任务
        Task task = validateTask(userId, taskId);
        if (task.getAssignee() != null && task.getAssignee().equals(assigneeUserId)) { // 校验当前审批人和被转派人不是同一人
            throw new RuntimeException("任务转办失败，转办人不存在");
        }
        // todo 1.2 校验目标用户存在

        // 2. 添加转办意见
        taskService.addComment(taskId, task.getProcessInstanceId(), BpmCommentTypeEnum.TRANSFER.getType(),
                BpmCommentTypeEnum.TRANSFER.formatComment(userId, assigneeUserId, reason));

        // 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
//        taskService.setOwner(taskId, task.getAssignee());
        taskService.setOwner(taskId, userId);
        // 3.2 执行转派（审批人），将任务转派给 assigneeUser
        // 委托（ delegate）和转办（transfer）的差别，就在这块的调用！！！！
        taskService.setAssignee(taskId, assigneeUserId);
    }


    @Override
    public List<UserTask> getUserTaskListByReturn(String userId, String taskId) {
        // 1.1 校验当前任务 task 存在
        Task task = validateTask(userId, taskId);
        // 1.2 根据流程定义获取流程模型信息
        BpmnModel bpmnModel = bpmnModelService.getBpmnModelByDefinitionId(task.getProcessDefinitionId());
        FlowElement source = BpmnModelUtils.getFlowElementById(bpmnModel, task.getTaskDefinitionKey());
        if (source == null) {
            return null;
        }

        // 2.1 查询该任务的前置任务节点的 key 集合
        List<UserTask> previousUserList = BpmnModelUtils.getPreviousUserTaskList(source, null, null);
        if (CollUtil.isEmpty(previousUserList)) {
            return Collections.emptyList();
        }
        // 2.2 过滤：只有串行可到达的节点，才可以回退。类似非串行、子流程无法退回
        previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null));
        log.info("可回退的任务节点：{}", previousUserList.size());
        return previousUserList;
    }

}
